﻿// SocketQR.cpp : 实现文件
//

#include "stdafx.h"
#include "GHCY.h"
#include "../Hui/Hui.h"
#include "Car.h"
#include "GException.h"
#include "LogList.h"
#include "PointSocket.h"
#include "SocketQR.h"

// SocketQR

#ifdef _DEBUG
	#define new DEBUG_NEW
#endif

CString SocketQR::m_strID1;
CString SocketQR::m_strQR1;
//CString SocketQR::m_strID2;
//CString SocketQR::m_strID3;

// 参考：
// OMRON PLC 以态网模块手册 w421-e1-04_cs1w_cj1w-etn21.pdf，FINS/TCP Method P186
// 返回错误码，OMRON PLC 以态网模块手册 w421-e1-04_cs1w_cj1w-etn21.pdf，FINS/TCP Method P120

UINT SocketQR::threadPro(LPVOID pParam) // 线程处理函数，LPVOID pParam 指向本类
{
	return ((SocketQR *)pParam)->threadLoop();
}

/// <作者>Mr. Hu</作者>
/// <日期>20250305</日期>
/// <功能>构造二维码通讯</功能>
/// <参数 name = "strIP">字符串形式IP地址</参数>
/// <参数 name = "port">端口</参数>
/// <返回></返回>
/// <说明>不能在线程中使用，收不到OnConnect等消息
/// CAsyncSocket已经创建线程，不用再加线程</说明>
SocketQR::SocketQR(LPCTSTR strIP, int port )
	: m_nSID(0)
	, m_nTick( 0 )
	, m_sockQR(INVALID_SOCKET)
	//, m_bConnected(false)
	, m_strIP(strIP)
	, m_nPort( port )
	, m_bTimeOut(FALSE)
{
	//if (m_strIP.IsEmpty())
	//{
	//	m_strIP.Format(L"%i.%i.%i.%i",
	//		GETBYTE(theApp.m_nComputerIP, 3),
	//		GETBYTE(theApp.m_nComputerIP, 2),
	//		GETBYTE(theApp.m_nComputerIP, 1),
	//		GETBYTE(theApp.m_nComputerIP, 0)
	//	);
	//}

	m_strIPPort.Format( L"%s:%i", (LPCTSTR)m_strIP, m_nPort );

	//m_hEventKilled = CreateEvent(NULL, FALSE, FALSE, NULL); // auto reset, initially reset

	ZeroMemory( m_pRecBuf, sizeof( m_pRecBuf ) );

	// 仿真状态，IP = 127.0.0.1
	//if (theApp.m_bDemo || theApp.m_bDemoCar)
	//{
	//	//int fin = m_strIP.ReverseFind('.');
	//	//ASSERT(fin >= 0);
	//	//LPCTSTR p = (LPCTSTR)m_strIP + fin + 1;
	//	//m_nPort = 10000 + _wtoi(p);
	//	m_strIP = L"127.0.0.1";
	//}
}

//SocketQR::~SocketQR()
//{
//}


// SocketQR 成员函数

// 20170321 改用UDP，因为连接不稳定
// 连接，PLC是服务端，上位机是客户端，LPCTSTR lpszHostAddress PLC地址, UINT nHostPort PLC端口。
// http://www.cnblogs.com/ayanmw/archive/2012/05/03/2480854.html
// http://bbs.csdn.net/topics/392069164
// http://blog.csdn.net/tuan891205/article/details/8516185
// http://blog.csdn.net/liujiayu2/article/details/51726041
BOOL SocketQR::connect()
{
	//if( m_bKill )	// 再判断一次，因为执行上一句时，这个值可能被主进程改变
	//	return FALSE;

	//PCTSTR lpszHostAddress = m_pCar->m_strIP;	// IP地址
	//int nHostPort = m_pCar->m_nPort;			// 端口

	//创建UDP socket
	m_sockQR = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);	// udp方式创建SOCKET
	if (m_sockQR == INVALID_SOCKET)
	{
		socketError(2028, L"socket() 错误。");

		return FALSE;
	}

	//if( m_bKill )	// 再判断一次，因为执行上一句时，这个值可能被主进程改变
	//	return FALSE;

	// QR地址
	USES_CONVERSION;
	memset( &m_ipHis, 0, sizeof(m_ipHis) );
	m_ipHis.sin_family			= AF_INET;
	if(m_strIP.IsEmpty())
		m_ipHis.sin_addr.s_addr		= htonl(INADDR_ANY);		// 任意客户端地址，接收
	else
		InetPton(AF_INET, m_strIP, &m_ipHis.sin_addr.s_addr);	//https://baike.baidu.com/item/inet_pton/9555884?fr=aladdin
	m_ipHis.sin_port			= htons( u_short(m_nPort) );	// 这里本地和远程用同一个端口

	//set Recv and Send time out
	int nTransmitTimeout = int( 20 * 1000 );		// 20秒，速度100时可以走2米
	int iResult = setsockopt(m_sockQR, SOL_SOCKET, SO_SNDTIMEO, (char *)&nTransmitTimeout, sizeof(nTransmitTimeout) );
	if (iResult == SOCKET_ERROR) {
		// 保存日志
		socketError(2034, L"setsockopt() 设置发送超时错误。");
		return FALSE;
	}

	//if( m_bKill )	// 再判断一次，因为执行上一句时，这个值可能被主进程改变
	//	return FALSE;

	//本地网卡IP和端口绑定，否则随机分配地址，多次连接会阻塞
	// https://blog.csdn.net/CharlesJhonson/article/details/105450420
	// 1 - 1023：BSD保留端口。
	// 1024 - 5000：BSD临时端口
	// 5001 - 65535：BSD服务器（非特权）
	// https://www.dyxmq.cn/program/code/c-cpp/bind-random-port-for-a-socket.html
	// MFC]UDP通信的简单实现，https://blog.csdn.net/u010312937/article/details/79927460
	//SOCKADDR_IN client_addr;
	memset(&m_ipMy, 0, sizeof(m_ipMy));
	m_ipMy.sin_family = AF_INET;
	m_ipMy.sin_addr.s_addr = htonl(INADDR_ANY);		// 任意本地地址
	m_ipMy.sin_port = htons(u_short(m_nPort));		// 指定端口，否则随机分配地址，多次连接会阻塞，按地址分不同的端口，否则会发送到同一个socket中，这里本地和远程用同一个端口

	//下面这一步是绑定本地网卡IP，返回值为0，加快连接速度
	if (bind(m_sockQR, (sockaddr*)&m_ipMy, sizeof(m_ipMy)) == SOCKET_ERROR)
	{
		socketError(2037, L"bind() 绑定IP错误。");
	}

	// 
	iResult = setsockopt( m_sockQR, SOL_SOCKET, SO_RCVTIMEO, (char *)&nTransmitTimeout, sizeof(nTransmitTimeout) );
	if (iResult == SOCKET_ERROR) {
		// 保存日志
		socketError(2033, L"setsockopt() 设置接收超时错误。");
		return FALSE;
	}

	//const char MODE_ON[] = "PON";
	//const char MODE_AUTO[] = "SET_MODE AUTO";
	//const char MODE_MANUAL[] = "SET_MODE MANUAL";
	//sendData(MODE_ON, sizeof(MODE_ON));


	return TRUE;
}

// 发送数据, LPCSTR pBuf 数据缓冲, int len 数据长度
BOOL SocketQR::sendData( LPCSTR pBuf, int len )
{
	int r = sendto( m_sockQR, pBuf, len, 0, (sockaddr *)&m_ipHis, sizeof(m_ipHis) );	// UDP时，不存在的地址也不延时

	if (r == SOCKET_ERROR) {
		// 保存日志
		socketError(2009, L"send() 发送数据错误");
		return FALSE;
    }
	else if( r != len ) {
		// 保存日志
		GException ge;
		ge.m_nCode		= 2008;
		ge.m_strObject	= m_strIPPort;	// IP地址;
		ge.m_strSummary	= L"sendPLC() 发送数据长度错误，0长度为超时。";
		ge.m_strDetails.Format( L"发送长度：%i, 正确长度：%i"
			, r, len );
		theApp.m_pLogList->addLog( &ge );

		Sleep(2000);  // 不成功时加2秒延时，否则日志更新太快

		return FALSE;
    }

	return TRUE;
}

/// <作者>Mr. Hu</作者>
/// <日期>20241002</日期>
/// <功能>接收QR数据</功能>
/// <参数 name = "len">接收最大长度</参数>
/// <返回>实际接收长度，-1不成功</返回>
/// <说明></说明>
int SocketQR::receiveData( int len )
{
	ASSERT( len < sizeof( m_pRecBuf ) );

	int addrlen = sizeof(m_ipHis);
	int r = recvfrom( m_sockQR, (PSTR)m_pRecBuf, sizeof( m_pRecBuf ), 0, (sockaddr *)&m_ipHis, &addrlen );
	if (theApp.m_mutexExit.isLocked())						// 正在退出，中断
		return -1;

	if( r == SOCKET_ERROR )
	{
		const char poff[] = "POFF";
		sendData(poff, sizeof(poff));									// 关闭扫描

		//ReturnWcs rw = {1,2,3,4,5,};
		//sendData((LPCSTR)&rw, sizeof(rw));

		socketError(2240, L"二维码通讯超时。");
		return -1;
	}
	//// 20200314 取消SSID判断，是上一次的数据也没有问题
	//// 20200315 恢复SID判断，不对应时有可能上一条消息没有收到
	//1111else if(m_nSID != m_pRecBuf[9])		// 20180617 由于通讯原因，可能会收到上一次的数据，递归执行接收程序
	//{
	//	// 保存日志
	//	GException ge;
	//	ge.m_nCode = 2043;
	//	ge.m_strObject = m_strIPPort;	// IP地址;
	//	ge.m_strSummary = L"receiveData() 接收数据SID错误。";
	//	ge.m_strDetails.Format(L"发送编号 %i, 接收编号 %i"
	//		, m_nSID, m_pRecBuf[9]);

	//	theApp.m_pLogList->addLog(&ge);

	//	//Sleep(20);	// 等20ms
	//	//return receiveData(len);
	//}
//#ifndef _DEBUG
//	//发布时解开注释
//#endif
//	else if( r != len ) {
//		// 保存日志
//		GException ge;
//		ge.m_nCode		= 2004;
//		ge.m_strObject	= m_strIPPort;	// IP地址;
//		ge.m_strSummary	= L"receivePLC() 接收数据长度错误，0长度为超时。";
//		ge.m_strDetails.Format( L"接收长度：%i, 正确长度：%i，数据："
//			, r, len );
//
//		CString & str = ge.m_strDetails;
//		for( int i = 0; i < r; i++ )
//		{
//			PCTSTR s = i & 0xf ? i & 3 ? L" %02X" : L"  %02X" : L"\r\n%02X";
//			str.AppendFormat( s, m_pRecBuf[i] );
//		}
//
//		theApp.m_pLogList->addLog( &ge );
//
//		//Sleep( 100 );	// 
//		
//		//m_pCar->setPos( 0, -4 );	// send() 接收数据长度错误。
//
//		onScoketError();
//
//		return FALSE;
//    }

	return r;
}

// 线程中循环传输
// 两托800程序
/*BOOL SocketQR::threadLoop()
{
	 //int t1 = GetTickCount(), t2 = t1;

	while (1)
	{
		if (connect())
			break;
	}

	while(1)
	{

#ifdef _DEBUG
		SocketQR::m_strID1 = "11111111111111";
		SocketQR::m_strID2 = "22222222222222";
		SocketQR::m_strID3 = "33333333333333";
#endif

		if (theApp.m_mutexExit.isLocked())	//正在退出，中断
			break;

		//{	// 加一对大括号，及时解除锁定
		//	HLock hl(&m_mutexQR, -1, -1);	// 锁住 m_strQR, m_strProductID，否则不安全

		//	if (PointSocket::m_bResetQR)
		//	{
		//		m_strID1 = L"";		// 清除产品ID
		//		m_strID2 = L"";		// 清除产品ID
		//		m_strID3 = L"";		// 清除产品ID
		//		//m_nProductSerial = 0;		// 清除产品序号

		//		continue;
		//	}
		//}

		// 800第一托范围
		const float nLimit = 300;
		bool bID1Empty = m_strID1.IsEmpty();
		bool bID2Empty = m_strID2.IsEmpty();
		bool bID3Empty = m_strID3.IsEmpty();
		bool b1 = (bID1Empty
			&& PointSocket::m_fDistance > (2550 - nLimit)
			&& PointSocket::m_fDistance < (2550 + nLimit)
			);

		// 800第二托范围
		bool b2 = (bID2Empty
			&& PointSocket::m_fDistance > (1500 - nLimit)
			&& PointSocket::m_fDistance < (1500 + nLimit)
			);

		// 715范围
		bool b3 = (bID3Empty
			&& PointSocket::m_fDistance > 2300 - nLimit
			&& PointSocket::m_fDistance < 2300 + nLimit
			);

		if (!PointSocket::m_bScan
			|| !(b1 || b2 || b3) )
			continue;										// 不在扫描范围，返回

		const char pon[] = "PON";
		sendData(pon, sizeof(pon));							// 启动扫描
		Sleep(10);
		sendData(pon, sizeof(pon));							// 发两次，保险

		int len = receiveData(QR_DATA_LENGTH);				// 阻塞方式接收数据
		if (len == SOCKET_ERROR)
		{
			m_strQR = L"";
			continue;
		}

		//t2 = GetTickCount();
		//m_nTick = t2 - t1;
		//t1 = t2;

		m_pRecBuf[len] = 0;

		// 回调函数 https://blog.csdn.net/fairytailq/article/details/78376414#:~:text=MFC%E4%B8%AD%E5%BA%94%E8%AF%A5%E6%9C%89%E4%B8%A4%E7%B1%BB%E5%9B%9E
		//ASSERT(pCallBack);
		//if(pCallBack)
		//	pCallBack(m_pRecBuf, len);

		HLock hl(&m_mutexQR, -1, DWORD(-1));	// 锁住 m_strQR, m_strProductID，否则不安全

		// 二维码长度固定22位
		if (len != 22)
		{
			m_strQR = L"";
			continue;
		}

		// 重新计算范围，因为receiveData会阻塞一段时间，PointSocket::m_fDistance会变化
		// 800第一托范围
		b1 = (bID1Empty
			&& PointSocket::m_fDistance > (2550 - nLimit)
			&& PointSocket::m_fDistance < (2550 + nLimit)
			);

		// 800第二托范围
		b2 = (bID2Empty
			&& PointSocket::m_fDistance > (1500 - nLimit)
			&& PointSocket::m_fDistance < (1500 + nLimit)
		);

		// 715范围
		b3 = (bID3Empty
			&& PointSocket::m_fDistance > 2300 - nLimit
			&& PointSocket::m_fDistance < 2300 + nLimit
			);

		// 取产品ID
		m_strQR = m_pRecBuf;
		//m_strProductID = m_strQR.Mid(12, 6);
		// 250308 0222 02 060025 1018
		// 日期6位 + 工控机终端编码4位 + 打码任务序号2位 + 产品编码id6位 + 流水序号4位。 @胡丛林 第11，12位是任务号来的，拿这个加产品编码去区分
		if (b1)
		{
			//m_strID1 = m_strQR.Mid(12, 6);
			m_strID1 = m_strQR.Mid(4, 14);
		}

		if (b2)
		{
			//m_strID2 = m_strQR.Mid(12, 6);
			m_strID2 = m_strQR.Mid(4, 14);
		}

		if (b3)
		{
			m_strID3 = m_strQR.Mid(4, 14);
			//m_strID1 = L"";												// 如果扫到715，不要1
		}
	}

	return 0;
}
*/

// 线程中循环传输
// 单托800程序
BOOL SocketQR::threadLoop()
{
	//int t1 = GetTickCount(), t2 = t1;

	while (1)
	{
		if (connect())
			break;
	}

	ULONGLONG t1, t2;
	QueryInterruptTime(&t2);
	t1 = t2;

	while (1)
	{

#ifdef _DEBUG
		SocketQR::m_strID1 = "11111111111111";
		SocketQR::m_strQR1 = "aaaa11111111111111";
		//SocketQR::m_strID2 = "22222222222222";
		//SocketQR::m_strID3 = "33333333333333";
#endif

		if (theApp.m_mutexExit.isLocked())		// 正在退出，中断
			break;

		QueryInterruptTimePrecise(&t2);			// 100ns 	Mincore.lib
		DWORD nTick = DWORD((t2 - t1) / 10000);		// ms
		t1 = t2;
		if (nTick < 200)
			Sleep(200 - nTick);

		if (theApp.m_mutexExit.isLocked())		// 正在退出，中断
			break;

		if (!PointSocket::m_bScan)
			continue;										// 不在扫描范围，返回

		// 开始扫描，先清原有二维码
		//SocketQR::m_strID1 = L"";		// 清除产品ID
		//SocketQR::m_strID2 = L"";		// 清除产品ID
		//SocketQR::m_strID3 = L"";		// 清除产品ID
		//m_strQR = L"";					// 清二维码
		
		const char pon[] = "PON";
		sendData(pon, sizeof(pon));							// 启动扫描
		Sleep(10);
		sendData(pon, sizeof(pon));							// 发两次，保险

		int len = receiveData(QR_DATA_LENGTH);				// 阻塞方式接收数据
		if (len == SOCKET_ERROR)
		{
			//m_strQR = L"";
			continue;
		}

		m_pRecBuf[len] = 0;
		//m_strQR = L"";

		// 回调函数 https://blog.csdn.net/fairytailq/article/details/78376414#:~:text=MFC%E4%B8%AD%E5%BA%94%E8%AF%A5%E6%9C%89%E4%B8%A4%E7%B1%BB%E5%9B%9E
		//ASSERT(pCallBack);
		//if(pCallBack)
		//	pCallBack(m_pRecBuf, len);

		HLock hl(&m_mutexQR, -1, DWORD(-1));	// 锁住 m_strQR, m_strProductID，否则不安全

		// 二维码长度固定22位
		if (len != 22)
		{
			m_strQR = L"";
			continue;
		}

		// 取产品ID
		m_strQR = m_pRecBuf;
		//m_strProductID = m_strQR.Mid(12, 6);
		// 250308 0222 02 060025 1018
		// 日期6位 + 工控机终端编码4位 + 打码任务序号2位 + 产品编码id6位 + 流水序号4位。 @胡丛林 第11，12位是任务号来的，拿这个加产品编码去区分
		//m_strID1 = m_strQR.Mid(12, 6);
		m_strQR1 = m_strQR;
		m_strID1 = m_strQR.Mid(4, 14);
	}

	return 0;
}

// 结束通讯线程，这段程序代码在SocketQR.cpp中，由主线程调用
void SocketQR::killThread()
{
	//if( theApp.m_pLogList )
	//	theApp.m_pLogList->criticalLog.Lock();	// 防止线程正在写日志

	//mutexExit.Lock();	// 线程互斥，防止通讯工作中杀死线程

	// 先关闭连接
	if (m_sockQR != INVALID_SOCKET)
	{
		shutdown(m_sockQR, SD_BOTH);
		closesocket( m_sockQR );
		m_sockQR = INVALID_SOCKET;
	}

	// 强制结束线程
	if(m_pThreadQR)
	{
		m_pThreadQR->m_bAutoDelete = TRUE;
		//DWORD ExitCode;
		//GetExitCodeThread(m_pThread->m_hThread, &ExitCode);
		//TerminateThread(m_pThread->m_hThread, ExitCode);			// 强制终止线程，有内存泄漏
		WaitForSingleObject(m_pThreadQR->m_hThread, 5000);			// 等待线程结束
		m_pThreadQR = 0;
	}

	//mutexExit.Unlock();

	//if( theApp.m_pLogList )
	//	theApp.m_pLogList->criticalLog.Unlock();	// 防止线程正在写日志

	//m_bKill = TRUE;	// 结束线程循环

	// DEBUG状态下长期等待，发布状态等待500ms后强制结束
//#ifdef _DEBUG
//	WaitForSingleObject(m_hEventKilled, INFINITE);
//#else
//	WaitForSingleObject(m_hEventKilled, 1000);
//#endif
}

/// <作者>Mr. Hu</作者>
/// <日期>20241005</日期>
/// <功能>网络错误</功能>
/// <参数 name = "nErrorCode">错误代码</参数>
/// <参数 name = "strSummary">错误名称</参数>
/// <返回></返回>
/// <说明>错误存入日志</说明>
void SocketQR::socketError(int nErrorCode, LPCTSTR strSummary)
{
	// 显示网络错误信息 https://learn.microsoft.com/zh-cn/windows/win32/debug/retrieving-the-last-error-code
	LPTSTR lpMsgBuf = 0;
	//LPVOID lpDisplayBuf;
	DWORD dw = WSAGetLastError();
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0, NULL);

	// 保存日志
	GException ge;
	ge.m_nCode = nErrorCode;
	ge.m_strObject = m_strIPPort;	// IP地址;
	ge.m_strSummary = strSummary;
	ge.m_strDetails.Format(L"错误%i, %s"
		, dw, lpMsgBuf);

	theApp.m_pLogList->addLog(&ge);

	Sleep(2000);  // 不成功时加2秒延时，否则日志更新太快
}

/// <作者>Mr. Hu</作者>
/// <日期>20241121</日期>
/// <功能>启动线程，接收数据</功能>
/// <参数 name = ""></参数>
/// <返回></返回>
/// <说明></说明>
void SocketQR::beginThread()
{
	m_pThreadQR = AfxBeginThread(SocketQR::threadPro, (LPVOID)this, THREAD_PRIORITY_TIME_CRITICAL);
}